// Package checksum worker_target.go 定义目标数据库校验工作线程
package checksum

import (
	"errors" // 导入 errors 包，用于错误判断
	"fmt"    // 导入 fmt 包，用于格式化字符串
	"time"   // 导入 time 包，用于处理时间，例如休眠

	log "github.com/sirupsen/logrus" // 导入 logrus 日志库

	"px-table-checksum-go/config" // 导入配置包
	"px-table-checksum-go/db"     // 导入数据库操作包
	"px-table-checksum-go/queue"  // 导入队列接口包
	"px-table-checksum-go/util"   // 导入 util 包以使用 KeyString 相关的工具函数
)

// RunTargetChecksumWorker 运行目标数据库校验工作线程
//
// 该函数启动一个目标端工作线程。它会持续从任务队列 `taskQueue` 中获取任务。
// 如果获取到的是正常的校验任务 (ChecksumTask)，它会在目标数据库上计算对应数据块的校验和，
// 并将结果写入校验数据库。
// 如果获取到的是结束信号任务 (ChunkNo == -1)，它会记录该信号并继续等待下一个任务。
// 当队列被关闭且队列中所有任务都被处理完毕后，Get() 操作会返回错误 (queue.ErrQueueClosed)，
// 此时工作线程会记录日志并正常退出。
//
// 参数:
//   - workerID: 工作线程的唯一标识符，主要用于日志记录。
//   - taskQueue: 任务队列 (实现了 queue.Queue 接口)，工作线程从中获取任务。
func RunTargetChecksumWorker(workerID int, taskQueue queue.Queue) {

	// 创建带有 worker ID 和类型标记的日志记录器
	logger := log.WithFields(log.Fields{
		"worker": workerID,
		"type":   "target", // 标记为目标端 worker
	})

	logger.Info("启动目标端Worker")

	// 获取目标数据库和校验数据库的连接池
	//targetDB := db.GetTargetDB()
	targetDB := db.GetDBManager().GetTargetDB()
	checksumDB := db.GetDBManager().GetChecksumDB()
	// 检查连接是否有效，如果无效则无法工作，直接退出
	if targetDB == nil || checksumDB == nil {
		logger.Error("目标数据库或Checksum数据库连接无效，Worker退出")
		return
	}

	// 获取目标库的标识符，用于写入校验结果
	dbID := config.GlobalConfig.Options.TargetDBID
	if dbID == "" {
		logger.Warn("目标数据库 ID (TargetDBID) 未在配置中设置，将使用 Target DB 的 host:port")
		dbID = config.GlobalConfig.DBTarget.ID() // 从数据库配置生成ID
	}
	logger.Debugf("目标数据库 ID: %s", dbID)

	// 主循环：持续从队列获取任务并处理
	for {
		// 从队列获取任务，Get() 会阻塞直到有任务或队列关闭
		taskInterface, err := taskQueue.Get()

		// 处理 Get() 返回的错误
		if err != nil {
			// 检查是否是队列关闭的特定错误
			if errors.Is(err, queue.ErrQueueClosed) {
				logger.Info("队列已关闭，目标端 worker 正常退出。")
				break // 队列关闭，退出循环
			}
			// 其他获取错误（例如 Redis 连接问题），记录错误并短暂休眠后重试
			logger.Errorf("从队列获取任务失败: %v。等待1秒后重试...", err)
			time.Sleep(1 * time.Second) // 避免错误循环消耗过多 CPU
			continue                    // 继续下一次循环尝试获取任务
		}

		// 类型断言：检查获取到的任务是否是期望的 *db.ChecksumTask 类型
		task, ok := taskInterface.(*db.ChecksumTask)
		if !ok {
			// 如果类型不匹配，记录警告并丢弃该任务，继续处理下一个
			logger.Warnf("从队列获取到非预期的任务类型 %T，任务被丢弃: %+v", taskInterface, taskInterface)
			continue
		}

		// 为当前处理的任务创建一个带上下文的日志记录器
		taskLogger := logger.WithFields(log.Fields{
			"schema": task.SchemaName,
			"table":  task.TableName,
			"chunk":  task.ChunkNo,
		})

		// 检查是否是结束信号任务 (ChunkNo == -1)
		if task.ChunkNo == -1 {
			logPrefix := fmt.Sprintf("收到表 %s.%s 的", task.SchemaName, task.TableName)
			if task.Error != "" {
				// 如果结束信号包含错误信息，记录为警告
				taskLogger.Warnf("%s【异常】结束信号: %s", logPrefix, task.Error)
			} else {
				// 正常结束信号，记录为信息
				taskLogger.Infof("%s【正常】结束信号", logPrefix)
			}

			continue // 处理完结束信号，继续等待下一个任务
		}

		// --------- 处理正常的校验计算任务 ----------
		taskLogger.Debugf("处理任务: 开始键=%s", task.StartKeyString)

		// 1. 解析任务中的起始键字符串
		// Modify: Use util.ParseKeyFromString
		startKeyValues, err := util.ParseKeyFromString(task.StartKeyString, task.NumKeyColumns)
		if err != nil {
			// 如果解析键失败，记录错误并跳过此块的处理
			taskLogger.Errorf("解析Key值'%s'失败: %v。跳过此chunk计算", task.StartKeyString, err)
			continue
		}
		// 将解析后的值填充回任务对象，虽然CalculateChunkChecksum内部也会做，但这里填充更清晰
		task.StartKeyValues = startKeyValues
		// 确保任务的 DBID 设置为目标数据库的 ID
		task.DBID = dbID

		// 2. 在目标数据库上执行校验和计算
		calcStartTime := time.Now()
		// 调用 calculator.go 中的 CalculateChunkChecksum 函数，在 targetDB 上执行
		result, _, calcErr := CalculateChunkChecksum(targetDB, task) // 注意：第二个返回值 isLastChunk 在目标端通常不关心
		calcTime := time.Since(calcStartTime)

		// 处理计算过程中可能发生的错误
		if calcErr != nil {
			taskLogger.Errorf("计算目标库Checksum失败: %v", calcErr)
			continue // 跳过此块，处理下一个任务
		}

		// 3. 将计算结果写入校验数据库
		if result != nil {
			result.DBID = dbID // 再次确认 DBID 是目标库的 ID
			writeStartTime := time.Now()
			// 调用 db 包中的 InsertChecksumResult 函数写入结果
			if writeErr := db.InsertChecksumResult(checksumDB, result); writeErr != nil {
				taskLogger.Errorf("写入目标库Checksum结果失败: %v。此chunk结果丢失", writeErr)
				continue // 跳过此块，处理下一个任务
			}
			writeTime := time.Since(writeStartTime)

			// 记录成功处理当前块的日志
			taskLogger.Debugf("完成: 行数=%d, CRC32=%d, 计算用时=%v, 写入用时=%v",
				result.RowCount, result.CRC32, calcTime, writeTime)
		} else {
			// 如果 CalculateChunkChecksum 返回 nil result 但没有错误，表示空块
			taskLogger.Info("在目标库查询为空块 (无数据)，未写入结果库")
		}
		// ------------------------------------------

	} // 结束 for 循环 (当队列关闭并为空时退出)

	// 记录 worker 退出日志
	logger.Info("目标端Worker退出")
}
